﻿using System;
using System.Collections;
using System.Collections.Generic;

namespace Fadd.JSON.Tokens
{
	/// <summary>
	/// JSON Object.
	/// </summary>
	public class Object : IContainerToken, IEnumerable<IToken>
	{
		private readonly List<IToken> _items = new List<IToken>();
		private readonly Dictionary<string, IToken> _properties = new Dictionary<string, IToken>();

		/// <summary>
		/// Initializes a new instance of the <see cref="Object"/> class.
		/// </summary>
		/// <param name="name">Name of object.</param>
		/// <param name="parent">Parent token.</param>
		public Object(string name, IContainerToken parent)
		{
			Parent = parent;
			Name = name;
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="Object"/> class.
		/// </summary>
		/// <param name="name">Name of object.</param>
		public Object(string name)
		{
			Name = name;
		}

		/// <summary>
		/// Gets the <see cref="Fadd.JSON.Tokens.IToken"/> at the specified index.
		/// </summary>
		/// <value></value>
		/// <exception cref="ArgumentOutOfRangeException"><c>index</c> is out of range.</exception>
		public IToken this[int index]
		{
			get
			{
				if (index >= _items.Count)
					throw new ArgumentOutOfRangeException("index", "Requested index " + index + ", collection contains " + _items.Count + " items.");
				return _items[index];
			}
		}

		#region IContainerToken Members

		/// <summary>
		/// Gets or sets parent object.
		/// </summary>
		public IContainerToken Parent { get; set; }

		/// <summary>
		/// Gets or sets name of object.
		/// </summary>
		public string Name { get; set; }

		/// <summary>
		/// Gets kind of token.
		/// </summary>
		public TokenId TokenId
		{
			get { return TokenId.Object; }
		}

		/// <summary>
		/// Add a child token
		/// </summary>
		/// <remarks>
		/// Will also assign current node as parent.
		/// </remarks>
		/// <param name="child">Child token</param>
		/// <exception cref="ArgumentNullException"><c>child</c> is null.</exception>
		/// <exception cref="System.ArgumentException">Already have a child with the specified name</exception>
		public void Add(IToken child)
		{
			if (child == null)
				throw new ArgumentNullException("child");

			child.Parent = this;
			_properties.Add(child.Name, child);
			_items.Add(child);
		}

		#endregion

		#region IEnumerable<IToken> Members

		/// <summary>
		/// Returns an enumerator that iterates through the collection.
		/// </summary>
		/// <returns>
		/// A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection.
		/// </returns>
		/// <filterpriority>1</filterpriority>
		public IEnumerator<IToken> GetEnumerator()
		{
			return _items.GetEnumerator();
		}

		/// <summary>
		/// Returns an enumerator that iterates through a collection.
		/// </summary>
		/// <returns>
		/// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
		/// </returns>
		/// <filterpriority>2</filterpriority>
		IEnumerator IEnumerable.GetEnumerator()
		{
			return GetEnumerator();
		}

		#endregion

		/// <summary>
		/// Get a child token.
		/// </summary>
		/// <param name="name">Name of child.</param>
		/// <returns>Token if found; otherwise null.</returns>
		/// <exception cref="ArgumentNullException"><c>name</c> is null.</exception>
		public IToken GetChild(string name)
		{
			if (name == null)
				throw new ArgumentNullException("name");

			IToken token;
			return _properties.TryGetValue(name, out token) ? token : null;
		}

		/// <summary>
		/// Add a child token
		/// </summary>
		/// <param name="name">Name of child token</param>
		/// <param name="value">Child token</param>
		/// <exception cref="ArgumentNullException"><c>name</c> or <c>value</c> is null.</exception>
		/// <exception cref="ArgumentException">Already contains a child with that name.</exception>
		public void Add(string name, IToken value)
		{
			if (name == null)
				throw new ArgumentNullException("name");
			if (value == null)
				throw new ArgumentNullException("value");

			_properties.Add(name, value);
			value.Parent = this;
			_items.Add(value);
		}

		/// <summary>
		/// Gets object name.
		/// </summary>
		/// <returns>Object name</returns>
		public override string ToString()
		{
			return Name;
		}
	}
}